BigQuery テーブルチューニングに効果的なクラスタリングが、既存テーブルにも追加できるようになりました!
こんにちは、みかみです。
BigQuery で、テーブル作成後にクラスタリングカラムを追加できるようになったそうです!
リリースノートによると、2020/10 からサポート開始されてたんですね。(しらなんだ。。
やりたいこと
- BigQuery の既存テーブルにクラスタリングを追加したい
- Python クライアントライブラリ経由でもクラスタリングを追加できるのか確認したい
クラスタリングって何?
BigQuery でクラスタ化テーブルを作成すると、テーブルのスキーマ内の 1 つ以上の列の内容に基づいてテーブルのデータが自動的に編成されます。指定した列は、関連するデータを同じ場所に配置するために使用されます。複数の列を使用してテーブルをクラスタ化する場合は、指定する列の順序が重要です。指定した列の順序によって、データの並べ替え順序が決まります。
ということで、AWS Redshift でいうところの Sort Key のようなものですね。
チューニング不要で大量データを高速にクエリ可能な BigQuery ですが、やはり処理データ量が多いとそれだけ時間はかかります。 また、SQL 実行時の処理ステップが多いほど時間がかかるので、必要に応じてあらかじめデータの並び替えをしておけば、効率よく SQL が実行できます。
- BigQuery 特集: ストレージの概要| Google Cloud ブログ
- [Cloud OnAir] BigQuery の仕組みからベストプラクティスまでのご紹介 2018年9月6日 放送 | SlideShare
注意事項
非クラスタ化テーブルがクラスタ化テーブルに変換、またはクラスタ化列セットが変更された場合、自動再クラスタリングはその時間以降にのみ使用できます。
既存テーブルにクラスタリングが追加できるようにはなったものの、現在のところ、既に格納済みのデータが自動で並び替えされることはないとのことです。
前提
bq
コマンドおよび BigQuery Python クライアントライブラリの実行環境は準備済みです。
動作確認時には Cloud Shell を使用しました。
- Python Client for Google BigQuery
- コマンドライン ツール リファレンス | BigQuery ドキュメント
- Cloud Shell の使用 | Cloud Shell ドキュメント
また、以下のテーブルを準備しました。
このテーブルに、クラスタリングを追加してみたいと思います。
bq コマンドでクラスタリングを追加・更新・削除
コマンドラインリファレンスの英語ドキュメントで、--clustering_fields
パラメータを確認できました。
※2021/04/23 現在、日本語のリファレスには、まだ --clustering_fields
パラメータの記載はありませんでした。
また、bq update --help
コマンド実行でも、クラスタリング変更用のパラメータが確認できます。
mikami_yuki@cloudshell:~ (cm-da-mikami-yuki-258308)$ bq update --help | grep clustering --clustering_fields: Comma separated field names. remove clustering on a table.
help 全文を表示してみると、こんな感じ。
mikami_yuki@cloudshell:~ (cm-da-mikami-yuki-258308)$ bq update --help (省略) --clustering_fields: Comma separated field names. Can only be specified for time based partitioned tables. Data will be first partitioned and subsequently "clustered on these fields. Set this to an empty string to remove clustering on a table. (省略)
テーブルカラムをカンマ区切りで指定すれば良いようです。 また、空文字列を指定すると、クラスタリング設定を削除することもできるようです。
コマンド実行して実際にクラスタリングが追加できるか試してみます。
mikami_yuki@cloudshell:~ (cm-da-mikami-yuki-258308)$ bq update --clustering_fields region,Date dataset_1.avocado_no_clustering Table 'cm-da-mikami-yuki-258308:dataset_1.avocado_no_clustering' successfully updated.
BigQuery 管理画面からも、クラスタリングが追加されたことが確認できました。
では、クラスタリングカラムを別のカラムに更新してみます。
mikami_yuki@cloudshell:~ (cm-da-mikami-yuki-258308)$ bq update --clustering_fields year dataset_1.avocado_no_clustering Table 'cm-da-mikami-yuki-258308:dataset_1.avocado_no_clustering' successfully updated.
パラメータで指定した通り、クラスタリングカラムが更新されました。
最後に、クラスタリングを削除してみます。
ikami_yuki@cloudshell:~ (cm-da-mikami-yuki-258308)$ bq update --clustering_fields '' dataset_1.avocado_no_clustering Table 'cm-da-mikami-yuki-258308:dataset_1.avocado_no_clustering' successfully updated.
クラスタリングが削除されたことが確認できました。
パーティションテーブルにクラスタリングを追加・更新・削除
先ほどは非パーティショニングテーブルにクラスタリングを追加してみましたが、パーティション設定済みのテーブルに対してもクラスタリングの update ができるのか確認してみます。
以下の、データロード日時を日単位でパーティショニング指定したテーブルを準備しました。
クラスタリングカラムを追加してみます。
mikami_yuki@cloudshell:~ (cm-da-mikami-yuki-258308)$ bq update --clustering_fields region,Date dataset_1.avocado_partitioning Table 'cm-da-mikami-yuki-258308:dataset_1.avocado_partitioning' successfully updated.
非パーティショニングテーブル同様、クラスタリングが追加できました。
念のため、更新と削除も問題なく実行できるか確認してみます。
mikami_yuki@cloudshell:~ (cm-da-mikami-yuki-258308)$ bq update --clustering_fields year dataset_1.avocado_partitioning Table 'cm-da-mikami-yuki-258308:dataset_1.avocado_partitioning' successfully updated.
mikami_yuki@cloudshell:~ (cm-da-mikami-yuki-258308)$ bq update --clustering_fields '' dataset_1.avocado_partitioning Table 'cm-da-mikami-yuki-258308:dataset_1.avocado_partitioning' successfully updated.
パーティショニングテーブルにも、クラスタリングを追加・更新・削除できることが確認できました。
Python クライアントライブラリでクラスタリングを追加
BigQuery Python クライアントライブラリのバージョンを確認します。
mikami_yuki@cloudshell:~ (cm-da-mikami-yuki-258308)$ pip3 list | grep bigquery google-cloud-bigquery 2.13.1 google-cloud-bigquery-storage 2.3.0
リファレンスドキュメントによると最新バージョンは 2.13.1
で、インストール済みのライブラリバージョンと同じです。
念のため、pip install -U
で、最新バージョンにアップデートします。
mikami_yuki@cloudshell:~/sample (cm-da-mikami-yuki-258308)$ pip3 install -U google-cloud-bigquery Requirement already up-to-date: google-cloud-bigquery in /usr/local/lib/python3.7/dist-packages (2.13.1) Requirement already satisfied, skipping upgrade: protobuf>=3.12.0 in /usr/local/lib/python3.7/dist-packages (from google-cloud-bigquery) (3.15.8) (省略) Requirement already satisfied, skipping upgrade: pycparser in /usr/local/lib/python3.7/dist-packages (from cffi>=1.0.0->google-crc32c<2.0dev,>=1.0; python_version >= "3.5"->google-resumable-media<2.0dev,>=0.6.0->google-cloud-bigquery) (2.20)
mikami_yuki@cloudshell:~/sample (cm-da-mikami-yuki-258308)$ pip3 list | grep bigquery google-cloud-bigquery 2.13.1 google-cloud-bigquery-storage 2.3.0
やはり、Cloud Shell にプリインストール済みの 2.13.1
が最新バージョンのようです。
update_table
でクラスタリングカラムを追加する、以下の Python コードを実行しました。
from google.cloud import bigquery table_id = 'cm-da-mikami-yuki-258308.dataset_1.avocado_no_clustering' client = bigquery.Client() table = client.get_table(table_id) print('clustering: {}'.format(table.clustering_fields)) table.clustering_fields = ['region','Date'] table = client.update_table(table, ['clustering_fields']) table = client.get_table(table_id) print('clustering: {}'.format(table.clustering_fields))
mikami_yuki@cloudshell:~/sample (cm-da-mikami-yuki-258308)$ python3 update_clustering.py clustering: None Traceback (most recent call last): File "update_clustering.py", line 9, in <module> table = client.update_table(table, ['clustering_fields']) File "/usr/local/lib/python3.7/dist-packages/google/cloud/bigquery/client.py", line 1098, in update_table partial = table._build_resource(fields) File "/usr/local/lib/python3.7/dist-packages/google/cloud/bigquery/table.py", line 928, in _build_resource return _helpers._build_resource_from_properties(self, filter_fields) File "/usr/local/lib/python3.7/dist-packages/google/cloud/bigquery/_helpers.py", line 717, in _build_resource_from_properties raise ValueError("No property %s" % filter_field) ValueError: No property clustering_fields
エラーです。。 (やはりクライアントライブラリはまだ対応してないのかな。。
念のため、クライアントライブラリのエラー発生箇所のコードを確認してみます。
(抜粋) def _build_resource_from_properties(obj, filter_fields): """Build a resource based on a ``_properties`` dictionary, filtered by ``filter_fields``, which follow the name of the Python object. """ partial = {} for filter_field in filter_fields: api_field = obj._PROPERTY_TO_API_FIELD.get(filter_field) if api_field is None and filter_field not in obj._properties: raise ValueError("No property %s" % filter_field) elif api_field is not None: partial[api_field] = obj._properties.get(api_field) else: # allows properties that are not defined in the library # and properties that have the same name as API resource key partial[filter_field] = obj._properties[filter_field] return partial (抜粋)
_PROPERTY_TO_API_FIELD
にも、テーブルプロパティにも定義されていないフィールドの更新は受け付けてくれないようです。
ということは、クラスタリング設定済みのテーブルであれば、更新はできそうです。
テーブルにクラスタリングを追加して、再度実行してみます。
mikami_yuki@cloudshell:~/sample (cm-da-mikami-yuki-258308)$ bq update --clustering_fields year dataset_1.avocado_no_clustering Table 'cm-da-mikami-yuki-258308:dataset_1.avocado_no_clustering' successfully updated. mikami_yuki@cloudshell:~/sample (cm-da-mikami-yuki-258308)$ python3 update_clustering.py clustering: ['year'] Traceback (most recent call last): File "update_clustering.py", line 10, in <module> table = client.update_table(table, ['clustering_fields']) File "/usr/local/lib/python3.7/dist-packages/google/cloud/bigquery/client.py", line 1098, in update_table partial = table._build_resource(fields) File "/usr/local/lib/python3.7/dist-packages/google/cloud/bigquery/table.py", line 928, in _build_resource return _helpers._build_resource_from_properties(self, filter_fields) File "/usr/local/lib/python3.7/dist-packages/google/cloud/bigquery/_helpers.py", line 717, in _build_resource_from_properties raise ValueError("No property %s" % filter_field) ValueError: No property clustering_fields
やはりエラーです。。 クライアントライブラリのコードをもう少しよくみてみます。
(抜粋) @clustering_fields.setter def clustering_fields(self, value): """Union[List[str], None]: Fields defining clustering for the table (Defaults to :data:`None`). """ if value is not None: prop = self._properties.setdefault("clustering", {}) prop["fields"] = value else: if "clustering" in self._properties: del self._properties["clustering"] (抜粋)
clustering_fields
の setter です。
テーブルオブジェクトのクラスタリングプロパティは、実際は clustering
という名前で定義されているようです。
実行する Python コードを、以下に修正して再実行します。
from google.cloud import bigquery table_id = 'cm-da-mikami-yuki-258308.dataset_1.avocado_no_clustering' client = bigquery.Client() table = client.get_table(table_id) print('clustering: {}'.format(table.clustering_fields)) table.clustering_fields = ['region','Date'] #table = client.update_table(table, ['clustering_fields']) table = client.update_table(table, ['clustering']) table = client.get_table(table_id) print('clustering: {}'.format(table.clustering_fields))
mikami_yuki@cloudshell:~/sample (cm-da-mikami-yuki-258308)$ python3 update_clustering.py clustering: ['year'] clustering: ['region', 'Date']
期待通り、クラスタリングカラムが更新できました。
では、Python クライアントコードの API パラメータフィールドにクラスタリング項目を追加してあげれば、クラスタリング未設定の既存テーブルにも、クラスタリング追加できるのでは?!(ほんとはそんなことやっちゃダメだけど。。
(抜粋) _PROPERTY_TO_API_FIELD = { "encryption_configuration": "encryptionConfiguration", "expires": "expirationTime", "external_data_configuration": "externalDataConfiguration", "friendly_name": "friendlyName", "mview_enable_refresh": "materializedView", "mview_query": "materializedView", "mview_refresh_interval": "materializedView", "partition_expiration": "timePartitioning", "partitioning_type": "timePartitioning", "time_partitioning": "timePartitioning", "view_use_legacy_sql": "view", "view_query": "view", "require_partition_filter": "requirePartitionFilter", # add mikami "clustering_fields": "clustering", } (抜粋)
テーブルのクラスタリングを削除した後、再度 python コードを実行してみます。
ikami_yuki@cloudshell:~ (cm-da-mikami-yuki-258308)$ bq update --clustering_fields '' dataset_1.avocado_no_clustering Table 'cm-da-mikami-yuki-258308:dataset_1.avocado_no_clustering' successfully updated. mikami_yuki@cloudshell:~/sample (cm-da-mikami-yuki-258308)$ python3 update_clustering.py clustering: None clustering: ['region', 'Date']
(いけたっぽい。) BigQuery 管理画面からも確認してみます。
クラスタリングが追加できました!
現時点では、Python クライアントライブラリではまだクラスタリングの追加がサポートされていませんが、この分ならすぐに対応されるかな?
まとめ(所感)
インデックスなしで高速クエリを実現するパワフルな BigQuery ですが、テーブルデータが大量になると、処理時間もそれなりに増えてしまいます。
基本的にはチューニング不要な BigQuery ではありますが、テーブルや SQL をチューニングすることで、より良いパフォーマンスが期待できます。
BigQuery のテーブルチューニングにおいて、パーティショニングとクラスタリングは重要なポイントなので、 クラスタリングが後からでも追加できるようになった今回のリリースは喜ばしいですね。
今後、格納済みデータの自動並び替えや、既存テーブルへのパーティショニングの追加もできるようになるとさらに嬉しいところです!
参考
- April 21, 2021 | BigQuery Release notes
- クラスタリング仕様の変更 | BigQuery ドキュメント
- クラスタ化テーブルの概要 | BigQuery ドキュメント
- BigQuery 特集: ストレージの概要| Google Cloud ブログ
- [Cloud OnAir] BigQuery の仕組みからベストプラクティスまでのご紹介 2018年9月6日 放送 | SlideShare
- コマンドライン ツール リファレンス | BigQuery ドキュメント
- Python Client for Google BigQuery
- googleapis/python-bigquery | GitHub